package com.itcoon.cloud.framework.webmvc.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.itcoon.cloud.framework.ex.register.IErrorCodeRegister;
import com.itcoon.cloud.framework.webmvc.config.properties.GlobalExceptionAlarmConfigProperties;
import com.itcoon.cloud.framework.webmvc.config.properties.GlobalWebConfigProperties;
import com.itcoon.cloud.framework.webmvc.exception.GlobalExceptionController;
import com.itcoon.cloud.framework.webmvc.exception.handler.*;
import com.itcoon.cloud.framework.webmvc.trace.HttpTraceLogFilter;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

@AutoConfiguration
@EnableConfigurationProperties({GlobalWebConfigProperties.class, GlobalExceptionAlarmConfigProperties.class})
public class GlobalWebConfig implements WebMvcConfigurer {

    private final GlobalWebConfigProperties properties;

    public GlobalWebConfig(GlobalWebConfigProperties properties) {
        this.properties = properties;
    }

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        AntPathMatcher antPathMatcher = new AntPathMatcher(".");
        for (GlobalWebConfigProperties.Api api : properties.getApis()) {
            for (String controller : api.getControllers()) {
                configurer.addPathPrefix(api.generalBasePath(), clazz->clazz.isAnnotationPresent(RestController.class) && antPathMatcher.match(controller, clazz.getPackage().getName()));
            }
        }
    }

    @Bean
    @ConditionalOnMissingBean(RestTemplate.class)
    public RestTemplate restTemplate(ObjectMapper objectMapper, RestResponseErrHandler errHandler){
        RestTemplate restTemplate = new RestTemplate();
        List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
        for (HttpMessageConverter<?> messageConverter : messageConverters) {
            if(MappingJackson2HttpMessageConverter.class.isAssignableFrom(messageConverter.getClass())){
                ((MappingJackson2HttpMessageConverter) messageConverter).setObjectMapper(objectMapper);
                break;
            }
        }
        restTemplate.setErrorHandler(errHandler);
        return restTemplate;
    }

    @Bean
    @ConditionalOnMissingBean(RestResponseErrHandler.class)
    public RestResponseErrHandler restResponseErrHandler(ObjectMapper objectMapper){
        return new RestResponseErrHandler(objectMapper);
    }

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        for (GlobalWebConfigProperties.Api api : properties.getApis()) {
            registry.addMapping(api.generalBasePath())
                    .allowedOriginPatterns("*")
                    .allowCredentials(true)
                    .allowedMethods("*")
                    .maxAge(3600);
        }
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public PseudoExceptionHandler pseudoExceptionHandler(){
        return new PseudoExceptionHandler();
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE + 10)
    public BaseExceptionHandler baseExceptionHandler(GlobalWebConfigProperties properties, IErrorCodeRegister errorCodeRegister){
        return new BaseExceptionHandler(properties, errorCodeRegister);
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE + 20)
    public BindExceptionHandler bindExceptionHandler(){
        return new BindExceptionHandler(properties);
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE + 30)
    public MethodArgumentNotValidExceptionHandler methodArgumentNotValidExceptionHandler(){
        return new MethodArgumentNotValidExceptionHandler(properties);
    }

    @Bean
    @Order(Ordered.LOWEST_PRECEDENCE)
    public DefaultExceptionHandler defaultExceptionHandler(){
        return new DefaultExceptionHandler();
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    @ConditionalOnMissingBean(GlobalExceptionController.class)
    public GlobalExceptionController globalExceptionController(List<GlobalExceptionHandler> handlers, ApplicationEventPublisher publisher){
        return new GlobalExceptionController(handlers, publisher);
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE + 15)
    public ConstraintViolationExceptionHandler constraintViolationExceptionHandler(){
        return new ConstraintViolationExceptionHandler(properties);
    }


    @Bean
    public HttpTraceLogFilter httpTraceLogFilter() {
        return new HttpTraceLogFilter(properties);
    }

//
//    @Bean
//    public GlobalExceptionListener globalExceptionListener(){
//        return new GlobalExceptionListener();
//    }

}
